package ui;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Vector;
import javax.swing.JPanel;
import utils.KeyList;

/**
 * @author duncan
 */
public class Playlist extends JPanel implements MouseListener, MouseMotionListener, KeyListener
{
    private Vector headers	    = new Vector();
    private Vector items	    = new Vector();
    private BoxStyle style_headers  = new BoxStyle(Color.DARK_GRAY, new Color(224, 127, 0), Color.BLACK, Color.BLACK, new Font("Serif", Font.BOLD, 12));
    private BoxStyle style_items1   = new BoxStyle(Color.GRAY, new Color(224, 127, 0), Color.BLACK, Color.BLACK, new Font("Serif", Font.PLAIN, 10));
    private BoxStyle style_items2   = new BoxStyle(Color.LIGHT_GRAY, new Color(224, 127, 0), Color.BLACK, Color.BLACK, new Font("Serif", Font.PLAIN, 10));

    private int mouse_x = 0, mouse_y	= 0;
    private int tempx	= 0, tempy	= 0;
    private boolean useHoverEffect	= true;
    private boolean dragAndDropEnabled	= true;
    private KeyList dragged		= null;
    private int dragPosition		= 0;
    private int selected		= -1;
    private boolean order		= false;
    private boolean isShiftDown		= false;
    private boolean isCTRLDown		= false;
    private boolean functionsOff	= true;
    
    /**
     * Playlist constructor.
     */
    public Playlist()
    {
	checkWidth();
	addMouseListener(this);
	addMouseMotionListener(this);
	addKeyListener(this);
	style_headers.loadBackground("img/NewUI/header_bar.png", "img/NewUI/header_bar_hover.png");
	style_headers.showBGImage = true;
    }

    /**
     * Adds a new header to the Playlist.
     * Values with a key with the same name
     * as a header appear under the header.
     *
     * @param header The header to add.
     * @return The index of the new header. Will be needed later.
     */
    public int addHeader(Collumn header)
    {
	headers.addElement(header);
	checkWidth();
	return headers.size() - 1;
    }

    /**
     * Sets the style of the headers.
     *
     * @param style The new style.
     */
    public void setStyleHeaders(BoxStyle style)
    {
	if (style != null) style_headers = style;
	repaint();
    }

    /**
     * Sets the dark color of the items
     *
     * @param style The new style.
     */
    public void setStyleItemsDark(BoxStyle style)
    {
	if (style != null) style_items1 = style;
	repaint();
    }

    /**
     * Sets the light color of the items
     *
     * @param style The new style.
     */
    public void setStyleItemsLight(BoxStyle style)
    {
	if (style != null) style_items2 = style;
	repaint();
    }

    /**
     * Removes the selected header if it exists in the Playlist.
     * Observe that this method doesn't modify the keys using the
     * header.
     *
     * @param header The header to remove.
     */
    public void removeHeader(int index)
    {
	headers.remove(index);
	checkWidth();
    }

    /**
     * A simple method used to debug the playlist.
     * It prints out all headers and values in the
     * playlist in the command-prompt.
     */
    public void debugList()
    {
	System.out.println("Debugging Playlist.");
	System.out.println("Header Count: " + headers.size());
	System.out.println("Pane width: " + getWidth());
	System.out.println("    Listing headers: ");
	for (int i = 0; i < headers.size(); i++)
	{
	    System.out.println("        " + ((Collumn) headers.get(i)).title + "   width:" + ((Collumn) headers.get(i)).width);
	}
	System.out.println("Finished Debugging!");
    }

    /**
     * Adds the specified item to the Playlist at the specified position.
     *
     * @param item The item to be added.
     * @param index The position in the list position it.
     */
    public void addItem(KeyList item, int index)
    {
	items.insertElementAt(item, index);
    }

    public void checkWidth()
    {
	if (headers.size() == 0) return;

	//If all components would have their preferred size, the sum would be 'current'.
	int current = 0;

	for (int i = 0; i < headers.size(); i++)
	{
	    ((Collumn) headers.get(i)).width = 8 * ((Collumn) headers.get(i)).title.length() + 16;
	    current += ((Collumn) headers.get(i)).width;
	}

	int min_size = 0;
	for (int i = 0; i < headers.size(); i++)
	{
	    ((Collumn) headers.get(i)).width += (getWidth() - current) * (((Collumn) headers.get(i)).width / current);
	    min_size += ((Collumn) headers.get(i)).width;
	}

	this.setPreferredSize(new Dimension(current, items.size() * 20 + 20));
    }

    /**
     * Selects an item in the list.
     *
     * @param index The item index to select.
     */
    public void selectItem(int index, boolean deselectOthers)
    {
	System.out.println("Selected object.");
	if (deselectOthers)
	    setAllSelected(false);

	if (index >= items.size()) return;
	if (index < 0) return;
	
	((KeyList) items.get(index)).selected = true;
	selected = index;
    }

    /**
     * deselects all items in the list.
     */
    public void setAllSelected(boolean selected)
    {
	for (int i = 0; i < items.size(); i++)
	{
	    ((KeyList) items.get(i)).selected = selected;
	}
    }

    /**
     * Paints the component.
     *
     * @param g The graphics component to paint on.
     */
    public void paint(Graphics g)
    {
	checkWidth();
	if (headers.size() < 1) return;

	//Clean the pane.
	g.clearRect(-1, -1, WIDTH + 1, HEIGHT + 1);

	g.setColor(getBackground());

	//Paint the background-color.
	g.fillRect(0, 0, getWidth(), getHeight());

	//Paint the headers
	int x = 0;

	//Loop through all headers and paint them on the graphics object.
	for (int i = 0; i < headers.size(); i++)
	{
	    int w = (int) ((Collumn) headers.get(i)).width;
	    g = Box.paint(g, x, 0, w , 20, ((Collumn) headers.get(i)).title, style_headers, mouseInZone(x, 0, w, 20));

	    //Loop through all items and paint them under the header.
	    for (int j = 0; j < items.size(); j++)
	    {
		if (Math.floor(j / 2.0d) == j / 2.0d)	//Paint the darker version
		    g = Box.paint(
			    g,											    //The graphics object
			    x,											    //The x coordinate to draw on.
			    20 + j * 20,									    //The y coordinate to draw on
			    w,											    //The width to draw on.
			    20,											    //The height to draw on
			    ((KeyList) items.get(j)).requestString(((Collumn) headers.get(i)).title),		    //The text to show
			    style_items1,									    //The style to use
				(mouseInZone(0, 20 + j * 20, getWidth(), 20)					    //Show highlight effect
				||(((KeyList) items.get(j)).selected)));					    //Show highlight effect
		
		else					//Paint the lighter version
		    g = Box.paint(g, x, 20 + j * 20, w, 20, ((KeyList) items.get(j)).requestString(((Collumn) headers.get(i)).title), style_items2, (mouseInZone(0, 20 + j * 20, getWidth(), 20) || (((KeyList) items.get(j)).selected)));
	    }

	    x += w;
	}

	//If an item is currently beeing dragged, paint a help-line
	if (dragged != null)
	{
	    g.setColor(Color.RED);
	    g.drawRect(0, (int) Math.floor(mouse_y / 20) * 20, getWidth(), 2);
	}
    }

    /**
     * Returns whatever or not the mouse is inside a specified rectangle.
     *
     * @param x
     * @param y
     * @param width
     * @param height
     *
     * @return If the mouse is inside the zone or not.
     */
    public boolean mouseInZone(int x, int y, int width, int height)
    {
	if (!useHoverEffect) return false;
	if ((mouse_x >= x)
	 && (mouse_y >= y)
	 && (mouse_x < x + width)
	 && (mouse_y < y + height))
	    return true;
	return false;
    }

    public void mouseEntered(MouseEvent e)
    {
	useHoverEffect = true;
	repaint();
    }

    public void mouseExited(MouseEvent e)
    {
	this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	useHoverEffect = false;
	repaint();
    }

    public void mouseDragged(MouseEvent e)
    {
	if (mouseInZone(0, 20, getWidth(), items.size() * 20))
	    this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	else
	    this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	mouse_x = e.getX();
	mouse_y = e.getY();
	repaint();
    }

    public void mouseMoved(MouseEvent e)
    {
	if (mouseInZone(0, 20, getWidth(), items.size() * 20))
	    this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	else
	    this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	mouse_x = e.getX();
	mouse_y = e.getY();
	repaint();
    }

    public void mouseClicked(MouseEvent e)
    {
	if (functionsOff) return;
	if (e.getButton() == MouseEvent.BUTTON1)
	{
	    if (mouseInZone(0, 0, getWidth(), 20))
	    {
		//Find header at mouse position
		int x = 0; String headerKey = "";
		for (int i = 0; i < headers.size(); i++)
		{
		    x += ((Collumn) headers.get(i)).width;
		    if (e.getX() < x)
		    {
			headerKey = ((Collumn) headers.get(i)).title;
			break;
		    }
		}

		items = OrderPlaylist.byName(items, headerKey, order);
		order = !order;
		
		repaint();
		for (int i = 0; i < items.size(); i++)
		{
		    System.out.println(((KeyList) items.get(i)).valueOf(headerKey));
		}
	    }
	}
    }

    /**
     * Turns drag and drop and some other unnesscesary functions on/off.
     *
     * @param turnFunctionsOff Should the functions be turned off?
     */
    public void setFunctionsOff(boolean turnFunctionsOff)
    {
	functionsOff = turnFunctionsOff;
    }
    
    public void mousePressed(MouseEvent e)
    {
	if (functionsOff) return;
	if (e.getButton() == MouseEvent.BUTTON1)
	{
	    tempx = e.getX();
	    tempy = e.getY();

	    if ((e.getY() > 20) && (e.getY() < 20 * items.size() + 20))
	    {
		//selected = -1;
		if (dragAndDropEnabled)
		{
		    System.out.println("You are attempting to move " + ((KeyList) items.get((e.getY() - 20) / 20)).valueOf("Name"));
		    dragged = (KeyList) items.get((e.getY() - 20) / 20);
		    dragPosition = (e.getY() - 20) / 20;
		    items.removeElementAt(dragPosition);
		    useHoverEffect = false;
		    //repaint();
		}
		else
		{
		    selectItem((e.getY() - 20) / 20, !isCTRLDown);
		}
	    }
	}
    }

    public void mouseReleased(MouseEvent e) 
    {
	if (functionsOff) return;
	if (e.getButton() == MouseEvent.BUTTON1)
	{
	    if ((e.getY() > 20) && (e.getY() < 20 * items.size() + 20) && (dragAndDropEnabled) && (dragged != null))
	    {
		setAllSelected(false);
		dragged.selected = true;
		items.insertElementAt(dragged, (e.getY() - 20) / 20);
		useHoverEffect = true;
		dragged = null;
		repaint();
	    }
	    else if ((e.getY() > 20 * items.size() + 20) && (dragAndDropEnabled) && (dragged != null))
	    {
		setAllSelected(false);
		dragged.selected = true;
		items.insertElementAt(dragged, items.size());
		useHoverEffect = true;
		dragged = null;
		repaint();
	    }
	    else if (dragged != null)
	    {
		setAllSelected(false);
		dragged.selected = true;
		items.insertElementAt(dragged, dragPosition);
		useHoverEffect = true;
		dragged = null;
		repaint();
	    }
	    if ((mouse_x == tempx) && (mouse_y == tempy))
	    {
		selected = (e.getY() - 20) / 20;
		selectItem((e.getY() - 20) / 20, !isCTRLDown);
		System.out.println("IsSelected!!!");
		repaint();
	    }
	}
	else if (e.getButton() == MouseEvent.BUTTON3)
	{
	    if ((e.getY() > 20) && (e.getY() < 20 * items.size() + 20))
	    {
		//Unsupported in this version.
	    }
	}
    }

    public void keyTyped(KeyEvent e)
    {

    }

    public void keyPressed(KeyEvent e)
    {
	if (functionsOff) return;
	System.out.println("KeyEvent");
	if (e.getKeyCode() == KeyEvent.VK_SHIFT)
	{
	    isShiftDown = true;
	    System.out.println("Shift is down.");
	}
	if (e.getKeyCode() == KeyEvent.VK_CONTROL)
	{
	    isCTRLDown = true;
	    System.out.println("CTRL is down.");
	}
    }

    public void keyReleased(KeyEvent e)
    {
	if (functionsOff) return;
	System.out.println("2KeyEvent");
	if (e.getKeyCode() == KeyEvent.VK_SHIFT)
	{
	    isShiftDown = false;
	    System.out.println("Shift is NOT down.");
	}
	if (e.getKeyCode() == KeyEvent.VK_CONTROL)
	{
	    isCTRLDown = false;
	    System.out.println("CTRL is NOT down.");
	}
    }
}